﻿/**
 * Peter
 * Created by: Peter Development Team
 *    http://peter.codeplex.com/
 * 
 * GNU General Public License version 2 (GPLv2)
 *    http://peter.codeplex.com/license
 *
 *  This code is provided on an AS IS basis, with no WARRANTIES,
 *  CONDITIONS or GUARANTEES of any kind.
 *  
 **/

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using Peter.Common.Interfaces;

namespace Peter.Logic
{
   /// <summary>
   /// Watches files for peter.
   /// </summary>
   public class PeterFileWatcher : IPeterFileWatcher
   {
      private readonly Collection <FileSystemWatcher> m_Watchers;
      private readonly Dictionary<string, Collection<IFileChangedNotification>> m_WatchedFiles;
      private readonly Dictionary<string, Collection<IFileChangedNotification>> m_WatchedDirectories;

      /// <summary>
      /// Initializes a new peter file watcher.
      /// </summary>
      public PeterFileWatcher ()
      {
         this.m_Watchers = new Collection <FileSystemWatcher> ();
         this.m_WatchedFiles = new Dictionary<string, Collection<IFileChangedNotification>> ();
         this.m_WatchedDirectories = new Dictionary<string, Collection<IFileChangedNotification>> ();
      }

      /// <summary>
      /// Adds a watch for the given file.
      /// </summary>
      /// <param name="file">File to add watch for.</param>
      /// <param name="notification">Object to notify for change.</param>
      public void AddWatch (FileInfo file, IFileChangedNotification notification)
      {
         if (file.Directory != null)
         {
            if (!file.Directory.Exists) return;
            if (!this.m_WatchedFiles.ContainsKey (file.FullName))
               this.m_WatchedFiles.Add (file.FullName, new Collection <IFileChangedNotification> ());
            this.m_WatchedFiles[file.FullName].Add (notification);
            if (this.m_Watchers.All (w => w.Path != file.Directory.FullName))
            {
               var watcher = new FileSystemWatcher (file.Directory.FullName)
                                {
                                   NotifyFilter =
                                      NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.Attributes
                                      | NotifyFilters.Size | NotifyFilters.LastWrite
                                };
               watcher.Changed += this.OnWatcherChanged;
               watcher.Created += this.OnWatcherCreated;
               watcher.Deleted += this.OnWatcherDeleted;
               watcher.Renamed += this.OnWatcherRenamed;
               watcher.EnableRaisingEvents = true;
               this.m_Watchers.Add (watcher);
            }
            // TODO: Verify that the watcher has a sufficient buffer...
         }
      }

      /// <summary>
      /// Adds a watch for the given directory.
      /// </summary>
      /// <param name="directory">Directory to add watch for.</param>
      /// <param name="notification">Object to notify for change.</param>
      public void AddWatch (DirectoryInfo directory, IFileChangedNotification notification)
      {
         if (!this.m_WatchedDirectories.ContainsKey (directory.FullName))
            this.m_WatchedDirectories.Add (directory.FullName, new Collection<IFileChangedNotification> ());
         this.m_WatchedDirectories[directory.FullName].Add (notification);
         if (this.m_Watchers.All (w => w.Path != directory.FullName))
         {
            var watcher = new FileSystemWatcher (directory.FullName)
                             {
                                NotifyFilter =
                                   NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.Attributes
                                   | NotifyFilters.Size | NotifyFilters.LastWrite
                             };
            watcher.Changed += this.OnWatcherChanged;
            watcher.Created += this.OnWatcherCreated;
            watcher.Deleted += this.OnWatcherDeleted;
            watcher.Renamed += this.OnWatcherRenamed;
            watcher.EnableRaisingEvents = true;
            this.m_Watchers.Add (watcher);
         }
      }

      /// <summary>
      /// Removes the watch for the given file.
      /// </summary>
      /// <param name="file">The file being watched.</param>
      /// <param name="notification">Notification to remove.</param>
      public void RemoveWatch (FileInfo file, IFileChangedNotification notification)
      {
         if (this.m_WatchedFiles.ContainsKey (file.FullName))
         {
            if (this.m_WatchedFiles[file.FullName].Contains (notification))
               this.m_WatchedFiles[file.FullName].Remove (notification);
            if (this.m_WatchedFiles[file.FullName].Count == 0)
            {
               this.m_WatchedFiles.Remove (file.FullName);
               if (file.Directory != null)
               {
                  foreach (var watcher in m_Watchers)
                  {
                     if (watcher.Path == file.Directory.FullName)
                     {
                        watcher.Dispose ();
                        this.m_Watchers.Remove (watcher);
                        break;
                     }
                  }
               }
            }
         }
      }

      /// <summary>
      /// Removes the watch for the given directory.
      /// </summary>
      /// <param name="directory">The directory being watched.</param>
      /// <param name="notification">Notification to remove.</param>
      public void RemoveWatch (DirectoryInfo directory, IFileChangedNotification notification)
      {
         if (this.m_WatchedDirectories.ContainsKey (directory.FullName))
         {
            if (this.m_WatchedDirectories[directory.FullName].Contains (notification))
               this.m_WatchedDirectories[directory.FullName].Remove (notification);
            if (this.m_WatchedDirectories[directory.FullName].Count == 0)
            {
               this.m_WatchedDirectories.Remove (directory.FullName);
               foreach (var watcher in m_Watchers)
               {
                  if (watcher.Path == directory.FullName)
                  {
                     watcher.Dispose ();
                     this.m_Watchers.Remove (watcher);
                     break;
                  }
               }
            }
         }
      }

      /// <summary>
      /// Occurs when a watched file or directory has been changed.
      /// </summary>
      /// <param name="sender">File System watcher.</param>
      /// <param name="e">FileSystemEventArgs</param>
      private void OnWatcherChanged (object sender, FileSystemEventArgs e)
      {
         if (this.m_WatchedFiles.ContainsKey (e.FullPath))
         {
            foreach (var watchedFile in m_WatchedFiles[e.FullPath])
            {
               watchedFile.FileChanged (e.FullPath);
            }
         }
         if (this.m_WatchedDirectories.ContainsKey (e.FullPath))
         {
            foreach (var watchedDirectory in m_WatchedDirectories[e.FullPath])
            {
               watchedDirectory.FileChanged (e.FullPath);
            }
         }
      }

      /// <summary>
      /// Occurs when a watched file or directory has been created.
      /// </summary>
      /// <param name="sender">File System watcher.</param>
      /// <param name="e">FileSystemEventArgs</param>
      private void OnWatcherCreated (object sender, FileSystemEventArgs e)
      {
         if (this.m_WatchedFiles.ContainsKey (e.FullPath))
         {
            foreach (var watchedFile in m_WatchedFiles[e.FullPath])
            {
               watchedFile.FileCreated (e.FullPath);
            }
         }
         if (this.m_WatchedDirectories.ContainsKey (e.FullPath))
         {
            foreach (var watchedDirectory in m_WatchedDirectories[e.FullPath])
            {
               watchedDirectory.FileCreated (e.FullPath);
            }
         }
      }

      /// <summary>
      /// Occurs when a watched file or directory has been renamed.
      /// </summary>
      /// <param name="sender">File System watcher.</param>
      /// <param name="e">RenamedEventArgs</param>
      private void OnWatcherRenamed (object sender, RenamedEventArgs e)
      {
         if (this.m_WatchedFiles.ContainsKey (e.FullPath))
         {
            foreach (var watchedFile in m_WatchedFiles[e.FullPath])
            {
               watchedFile.FileRenamed (e.FullPath, e.OldFullPath);
            }
         }
         if (this.m_WatchedDirectories.ContainsKey (e.FullPath))
         {
            foreach (var watchedDirectory in m_WatchedDirectories[e.FullPath])
            {
               watchedDirectory.FileRenamed (e.FullPath, e.OldFullPath);
            }
         }
      }

      /// <summary>
      /// Occurs when a watched file or directory has been deleted.
      /// </summary>
      /// <param name="sender">File System watcher.</param>
      /// <param name="e">FileSystemEventArgs</param>
      private void OnWatcherDeleted (object sender, FileSystemEventArgs e)
      {
         if (this.m_WatchedFiles.ContainsKey (e.FullPath))
         {
            foreach (var watchedFile in m_WatchedFiles[e.FullPath])
            {
               watchedFile.FileDeleted (e.FullPath);
            }
         }
         if (this.m_WatchedDirectories.ContainsKey (e.FullPath))
         {
            foreach (var watchedDirectory in m_WatchedDirectories[e.FullPath])
            {
               watchedDirectory.FileDeleted (e.FullPath);
            }
         }
      }
   }
}
